package fpzhan.plane.program.connect;

import com.alibaba.fastjson.JSONArray;
import fpzhan.plane.program.describe.CheckParamForLose;
import fpzhan.plane.program.exception.FlowNotComposeException;
import fpzhan.plane.program.exception.NotSetInitParamException;
import fpzhan.plane.program.function.CodeBlockFunction;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;

public class RunFlow {

    private String flowId;

    private String name;

    private ChainCodeBlockConnect chain;

    private List<ChainCodeBlockConnect> afterChains;


    private CodeBlockFunction flow;

    private JSONArray proxyJson = new JSONArray(new ArrayList<>());

    private JSONArray structJson = new JSONArray(new ArrayList<>());

    private Function<Param, Map<String, Object>> result = param -> param.variables();

    private CheckParamForLose<ChainCodeBlockCompose, ChainCodeBlockConnect> initCheck;

    private Consumer<Param> initFunction;

    public RunFlow(
            String flowName,
            CodeBlockContext context
    ) {
        this.name = flowName;
        this.flowId = UUID.randomUUID().toString();
        chain = new ChainCodeBlockConnect(context, this);
        this.initCheck = this.chain.execute(param -> initFunction.accept(param));
    }

    public ChainCodeBlockConnect flowBegin() {
        return chain;
    }

    public ChainCodeBlockConnect flowBegin(int index) {
        if (afterChains == null) {
            afterChains = new ArrayList<>();
        }
        int size = afterChains.size();
        if (size <= index) {
            for (int i = 0; i < (index - size + 1); i++) {
                afterChains.add(null);
            }
        }
        ChainCodeBlockConnect connect = new ChainCodeBlockConnect(chain.getContext(), this);
        afterChains.set(index, connect);
        return connect;
    }

    public void compose() throws Exception {
        if (!isCompose()) {
            if (afterChains != null) {
                for (ChainCodeBlockConnect connect : afterChains) {
                    if (connect != null) this.chain.chainConnect(connect);
                }
            }
            this.flow = this.chain.compose(proxyJson, structJson, null);
        }

    }

    private void checkCompose() throws FlowNotComposeException {
        if (this.flow == null) throw new FlowNotComposeException();
    }

    public RunResult run() throws Exception {
        compose();
        if (initFunction==null){
            throw new NotSetInitParamException();
        }
        Param param = new Param(this.flowId);
        this.flow.apply(param);
        RunResult runResult = new RunResult(proxyJson, result.apply(param));
        param.destroy();
        return runResult;
    }

    public JSONArray struct() throws Exception {
        compose();
        return structJson;
    }

    public InitParam initParam(Map<String, Object> map) {
        initFunction = param -> param.putAll(map);
        return new InitParam(this);
    }

    protected RunFlow init(String comment, String... keys) {
        this.initCheck.addc(keys).comment(comment);
        return this;
    }

    public String getName() {
        return name;
    }

    public boolean isCompose() {
        return this.flow != null;
    }


}
